# coding: utf8

""" Google Friend Connect (GFC) plugin for web2py

Author : Hans Christian v. Stockhausen <hc at vst.io>
Date   : 2011-02-06
WWW    : http://jhb.gtug.co.za

Use this plugin to replace the standard web2py login mechanism with Google 
Friend connect. http://www.google.com/friendconnect

Setup
-----
1 - In your HTML header include {{=plugin_friendconnect_init()}} to load and
    initialize the required JavaScript libraries.

2 - Make sure that your auth table has a username field for example by changing
    auth.define_tables() to auth.define_tables(username=True) in db.py.
    GFC does not share a user's email address with GFC sites.
    
3 - Update the config section with your GFC site Id further below.

"""


import os
import sys


__author__ = 'Hans Christian v. Stockhausen <hc at vst.io>'


## Configuration ------------------------------------------------------------->

# Please maintain your site's GFC site id below
GFC_SITE_ID = "02728805420134823654"

# Optionally customize the GFC user to auth attribute field mapping below where
# the key is the GFC attribute name and the value the auth field name.
FIELD_MAPPING = dict(id='username', displayName='first_name')

## ---------------------------------------------------------------------------<


if not GFC_SITE_ID:
    raise HTTP(500, 'Google Friend Connect site ID not set in configuration '
        'constant GFC_SITE_ID of web2py model file plugin_friendconnect.py')


# Load the required JavaScript libraries --------------------------------------
# As per module docstring please include a call to this function in your HTML
# header like so {{=plugin_friendconnect_init()}}
# Optionally you may include the name of a Javascript callback function that
# is triggerd and passed the GFC security token once the OpenSocialAPI has
# been loaded.

def plugin_friendconnect_init(js_callback=None):
    callback = '%s(securityToken)' % js_callback if js_callback else ''
    return XML("""
        <script type="text/javascript" src="http://www.google.com/jsapi">
        </script>
        <script type="text/javascript">
            google.load('friendconnect', '0.8');
        </script>
        <script type="text/javascript">
            function reloadOnAuthEvent(){
                if (!window.timesloaded) {
                    window.timesloaded = 1;
                } else {
                    window.location.href=window.location.href;
                }
            };
            google.friendconnect.container.setParentUrl('/');
            google.friendconnect.container.initOpenSocialApi({
                site: '%s',
                onload: function(securityToken) {
                    reloadOnAuthEvent();
                    %s; }
                });
        </script>""" % (GFC_SITE_ID, callback))


# Import opensocial library ---------------------------------------------------
# Usually the preferred way to import libraries from the modules directory of
# web2py is to use the local_import() helper. However due to some import inter-
# dependencies of the opensocial module we have to add the path to sys.path
# instead. We need to take care not to append to path multiple times as that 
# would introduce a memory leak.

OSPATH = os.path.join(request.env.web2py_path,
    'applications/%s/modules/plugin_friendconnect' % request.application)
if not OSPATH in sys.path:
    sys.path.extend([OSPATH, OSPATH+'/opensocial'])
    
import opensocial


# Custom login/logout ---------------------------------------------------------
# The class that follows is used to override web2py's default login/logout
# mechansim. When, for example, a user tries to access a resource that requires
# her to be logged into the site this class dictates what to do.

class GfcLogin(object):
    
    def __init__(self, field_mapping=FIELD_MAPPING):
        
        """ 
        The argument field_mapping maps GFC user attributes to w2p auth table 
        names. For example the GFC field id is by default stored in the auth
        table field first_name.
        """
        
        self.field_mapping = field_mapping
        
    def login_url(self, next='/'):
        """ 
        This method is invoked by web2py to discover the login page's URL
        """
        url = URL(c='plugin_friendconnect', f='login', vars=dict(next=next))
        return url
    
    def logout_url(self, next='/'):
        """
        This method is invoked by web2py's authentication system to discover
        the URL of the page users are to be sent to to log out of the system.
        """
        url = URL(c='plugin_friendconnect', f='logout', vars=dict(next=next))
        return url
    
    def get_user(self):
        """
        This method is invoked by web2py to retrieve the details of the 
        logged in user. It returns a dictionary of user attributes that
        correspond to fields in the auth_user table.
        """
        
        cookie = request.cookies.get('fcauth' + GFC_SITE_ID)
        if cookie:
            config = opensocial.ContainerConfig(
                security_token=cookie.value,
                security_token_param='fcauth',
                server_rpc_base='https://www.google.com/friendconnect/api/rpc',
                server_rest_base='https://www.google.com/friendconnect/api')
            container = opensocial.ContainerContext(config)
            gfc_request = opensocial.FetchPersonRequest(
                user_id='@me', 
                fields=['@all'])
            gfc_response = container.send_request(gfc_request)
            return dict((v, gfc_response[k]) for k, v in
                self.field_mapping.items())
    
        
# Tell w2p auth system to use our custom login/logout mechansim ...
auth.settings.login_form = GfcLogin()
# and disable unsupported auth actions.
auth.settings.actions_disabled=['register','change_password',
    'request_reset_password', 'profile']



# GFC Login/logout state ------------------------------------------------------
# GFC indicates whether a user is logged into the site or not by passing a 
# cookie with every http request. Since a user may decide to log into the site
# using GFC's widget rather than going through w2p's login mechanism as
# customized with GfcLogin above, we have to activaly check for the presence
# or absence of the cookie and change the user's login status accordingly.
# Calling login() and logout() will invoke our custom GfcLogin mechanism.

if request.cookies.get('fcauth' + GFC_SITE_ID) and not auth.is_logged_in():
    auth.login()
elif not request.cookies.get('fcauth' + GFC_SITE_ID) and auth.is_logged_in():
    auth.logout()


# Javascript constants to render login/logout widgets -------------------------
# The GFC SignInGadget as loaded in  JS_IS_LOGGED_IN could be used to handle
# both the logged-in and logged-out state however it renders a button. We use
# a SignInButton in text style instead as that is more in line with the default
# web2py look and feel.

JS_SignInButton = """   
    <script type="text/javascript">
        google.friendconnect.renderSignInButton({ 
            'id': 'gfc_login_form', 
            'style': 'text' });
    </script>
    """

JS_SignInGadget = """
    <script type="text/javascript">
        var skin = {};
        skin['BORDER_COLOR'] = '#ffffff';
        skin['ENDCAP_BG_COLOR'] = '#e0ecff';
        skin['ENDCAP_TEXT_COLOR'] = '#333333';
        skin['ENDCAP_LINK_COLOR'] = '#0000cc';
        skin['ALTERNATE_BG_COLOR'] = '#ffffff';
        skin['CONTENT_BG_COLOR'] = '#ffffff';
        skin['CONTENT_LINK_COLOR'] = '#0000cc';
        skin['CONTENT_TEXT_COLOR'] = '#333333';
        skin['CONTENT_SECONDARY_LINK_COLOR'] = '#7777cc';
        skin['CONTENT_SECONDARY_TEXT_COLOR'] = '#666666';
        skin['CONTENT_HEADLINE_COLOR'] = '#333333';
        skin['ALIGNMENT'] = 'right';
        google.friendconnect.container.renderSignInGadget({ 
            id: 'gfc_login_form',
            site: '%s'}, skin);
    </script>
    """ % GFC_SITE_ID

# Patch auth.navbar() ---------------------------------------------------------
# This plugin assumes that you use auth.navar() to render a login/logout menu
# as is the case in web2py's default layout.html file. Here we patch the 
# function to render a GFC widget instead subject to whether the user is looged
# in or not.

def custom_navbar(*_, **__): # ignore any args and kwargs
    html = SPAN(_id='gfc_login_form', _class='auth_navbar').xml()
    html += JS_SignInGadget if auth.is_logged_in() else JS_SignInButton
    return XML(html)

auth.navbar = custom_navbar



